﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace csis.ImmediatorClasses
{
	/// <summary>
	/// Preprocesses, compiles and runs scripts according to
	/// the specifies immediator options.
	/// </summary>
	public class Immediator
	{
		public Immediator(ImmediatorOptions options)
		{
			if (options == null)
			{
				throw new ArgumentNullException("options");
			}

			this.options = options;
		}

		public static bool Run(ImmediatorOptions options)
		{
			var immediator = new Immediator(options);
			var success = immediator.Run();
			return success;
		}

		private readonly ImmediatorOptions options;
		private Compiler compiler;

		public bool Run()
		{
			var compileSuccess = Compile();
			if (!compileSuccess)
			{
				return false;
			}

			if (this.options.NoExecution)
			{
				return true;
			}

			LaunchProgram();

			return true;
		}

		/// <summary>
		/// Contains a temporary file container if necessary. This ensures
		/// proper cleanup of the file after it has been executed and this
		/// program terminates.
		/// </summary>
		private TemporaryFile TemporaryExecutable { get; set; }

		/// <summary>
		/// Contains the file path to the executable that is to be 
		/// compiled and run.
		/// </summary>
		private string CompiledExecutable { get; set; }

		private bool Compile()
		{
			this.CreateExecutableFileName();
			this.options.CompilerOptions.OutputFile = this.CompiledExecutable;

			this.compiler = new Compiler(this.options.CompilerOptions);
			var success = compiler.Compile();

			// notify the compilation result to the shell
			// we cannot just output anything from here
			this.NotifyCompileErrors();
			return success;
		}

		private void CreateExecutableFileName()
		{
			if (this.options.ExecutableFile != null)
			{
				this.CompiledExecutable = this.options.ExecutableFile;
			}
			else
			{
				if (this.options.NoWait)
				{
					this.CompiledExecutable = Path.GetTempFileName();
				}
				else
				{
					this.TemporaryExecutable = new TemporaryFile();
					this.CompiledExecutable = TemporaryExecutable.FileName;
				}
			}
		}

		private void NotifyCompileErrors()
		{
			var handler = this.options.CompilerOptions.CompilerErrorHandler;
			if (handler != null)
			{
				handler(this.compiler.CompileErrors);
			}
		}

		private void LaunchProgram()
		{
			var psi = this.GetScriptProcessStartInfo();
			using (var process = Process.Start(psi))
			{
				if (!this.options.NoWait)
				{
					process.WaitForExit();
				}
			}
		}

		private ProcessStartInfo GetScriptProcessStartInfo()
		{
			var psi = new ProcessStartInfo(this.CompiledExecutable);
			psi.UseShellExecute = false;
			var arguments = new List<string>();
			if (this.options.FirstFileAsArgument)
			{
				arguments.Add(this.options.CompilerOptions.InputFiles.First());
			}
			else if (this.options.AllFilesAsArguments)
			{
				arguments.AddRange(this.compiler.CompiledScripts.Keys);
			}
			if (this.options.ScriptArguments != null)
			{
				arguments.AddRange(this.options.ScriptArguments);
			}
			psi.Arguments = String.Join(" ", arguments);
			if (this.options.FirstFileEnvVar != null)
			{
				psi.EnvironmentVariables[this.options.FirstFileEnvVar] = this.options.CompilerOptions.InputFiles.First();
			}
			if (this.options.AllFilesEnvVar != null)
			{
				psi.EnvironmentVariables[this.options.AllFilesEnvVar] = String.Join(" ", this.compiler.CompiledScripts.Keys.Select(x => '"' + x + '"'));
			}
			return psi;
		}
	}
}
